package cn.jtcoding.springframework.aop.aspect;

import cn.jtcoding.springframework.aop.annotation.Aspect;
import cn.jtcoding.springframework.aop.annotation.Order;
import cn.jtcoding.springframework.aop.proxy.factory.AopProxyFactory;
import cn.jtcoding.springframework.aop.proxy.factory.DefaultAopProxyFactory;
import cn.jtcoding.springframework.core.BeanContainer;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class AspectWeaver {

    private AopProxyFactory aopProxyFactory;

    public AspectWeaver() {
        aopProxyFactory = new DefaultAopProxyFactory();
    }

    private Set<Class<?>> aspectClassSet = null;
    Map<Class<? extends Annotation>, List<AspectInfo>> aspectCategory = new ConcurrentHashMap<>(6);

    public Set<Class<?>> getAspectClassSet() {
        if (aspectClassSet == null) {
            aspectClassSet = BeanContainer.getInstance().getClassesByAnnotation(Aspect.class);
            resolverAspectCategory();
            log.info("Load aspect class: {}", aspectClassSet);
            log.info("Category aspect class: {}", aspectCategory);
        }
        return aspectClassSet;
    }

    public Object wrapIfNecessary(Class<?> targetClass, Object instance) {
        if (getAspectClassSet().isEmpty() || shouldSkip(targetClass)) {
            return instance;
        }

        List<AspectInfo> targetClassAspectInfos = new ArrayList<>(6);
        for (Annotation annotation : targetClass.getAnnotations()) {
            List<AspectInfo> aspectInfos = aspectCategory.get(annotation.annotationType());
            if (aspectInfos == null || aspectInfos.isEmpty()) {
                continue;
            }
            targetClassAspectInfos.addAll(aspectInfos);
        }

        try {
            return aopProxyFactory.createAopProxy(instance, targetClassAspectInfos).getProxy();
        } catch (Exception e) {
            throw new RuntimeException("AOP error ", e);
        }
    }

    private void resolverAspectCategory() {
        for (Class<?> aspectClass : aspectClassSet) {
            if (!verifyAspectClass(aspectClass)) {
                continue;
            }
            Aspect aspect = aspectClass.getAnnotation(Aspect.class);
            Order order = aspectClass.getAnnotation(Order.class);
            List<AspectInfo> aspectInfos = aspectCategory.get(aspect.value());
            if (aspectInfos == null) {
                aspectInfos = new ArrayList<>(6);
                aspectInfos.add(new AspectInfo(order.value(), (DefaultAspect) BeanContainer.getInstance().getBean(aspectClass)));
                aspectCategory.put(aspect.value(), aspectInfos);
                continue;
            }
            aspectInfos.add(new AspectInfo(order.value(), (DefaultAspect) BeanContainer.getInstance().getBean(aspectClass)));
        }
    }

    private boolean shouldSkip(Class<?> targetClass) {
        return targetClass.isAnnotationPresent(Aspect.class) || DefaultAspect.class.isAssignableFrom(targetClass);
    }

    private boolean verifyAspectClass(Class<?> aspectClass) {
        return aspectClass.isAnnotationPresent(Aspect.class)
                && aspectClass.isAnnotationPresent(Order.class)
                && DefaultAspect.class.isAssignableFrom(aspectClass)
                && aspectClass.getAnnotation(Aspect.class).value() != Aspect.class;
    }
}
